﻿
using System;
using System.Data;
using System.Threading;
using System.Web;
namespace PageCache.Store.SQLServer
{
    
    public class SQLServerStoreDataList : IStoreDataList
    {
        const string tableName = "StoreDataList";


        SQLServerHelper dbh = null;
        Setting.Setting setting = null;


        public SQLServerStoreDataList(Setting.Setting setting, string connectionString)
        {
            this.setting = setting;

            dbh = new SQLServerHelper(connectionString);
        }


        public StoreData Get(string type, string key)
        {
            StoreData data = null;

            string sql = "select [KEY],[Type],[CreatedDate],[ExpiresAbsolute],[Seconds],[HeadersData],[BodyData] "
                + " from [" + tableName + "]   where [Type]=@Type and [Key]=@Key";

            try
            {
                using (var connection = dbh.CreateConnection())
                {
                    using (var command = dbh.CreateCommand())
                    {
                        command.CommandText = sql;
                        command.Connection = connection;

                        command.Parameters.Add(dbh.CreateParameter("@Type", type));
                        command.Parameters.Add(dbh.CreateParameter("@Key", key));

                        connection.Open();

                        using (var reader = command.ExecuteReader(CommandBehavior.SequentialAccess))
                        {

                            if (reader.Read())
                            {
                                data = new Store.StoreData();

                                data.Key = reader.GetString(0);
                                data.Type = reader.GetString(1);
                                data.CreatedDate = reader.GetDateTime(2);
                                data.ExpiresAbsolute = reader.GetDateTime(3);
                                data.Seconds = reader.GetInt32(4);

                                data.HeadersData = (byte[])reader.GetValue(5);
                                data.BodyData = (byte[])reader.GetValue(6);
                            }
                            reader.Close();
                            reader.Dispose();
                        }
                    }

                    connection.Close();
                }
            }
            catch (Exception ex)
            {

            }



            return data;
        }



        public void Add(string store, StoreData data)
        {
            if (data.ExpiresAbsolute <= DateTime.Now)
            {
                return;
            }

            try
            {
                object ex = dbh.ExecuteScalar<object>("select top 1 1 from [" + tableName + "] where [key]=@key and [type]=@type "
               , dbh.CreateParameter("@key", data.Key)
               , dbh.CreateParameter("@type", data.Type));


                if (ex == null || ex == DBNull.Value)
                {
                    dbh.ExecuteNoneQuery("insert into [" + tableName + "]([KEY],[Type],[HeadersData],[BodyData],[CreatedDate],[ExpiresAbsolute],[Seconds],[date],[store]) "
                        + " values(@KEY,@Type,@HeadersData,@BodyData,@CreatedDate,@ExpiresAbsolute,@Seconds,@date,@store)"

                    , dbh.CreateParameter("@KEY", data.Key)
                    , dbh.CreateParameter("@Type", data.Type)

                    , dbh.CreateParameter("@HeadersData", data.HeadersData)
                    , dbh.CreateParameter("@BodyData", data.BodyData)

                    , dbh.CreateParameter("@CreatedDate", data.CreatedDate)
                    , dbh.CreateParameter("@ExpiresAbsolute", data.ExpiresAbsolute)
                    , dbh.CreateParameter("@Seconds", data.Seconds)
                    , dbh.CreateParameter("@date", DateTime.Now)
                    , dbh.CreateParameter("@store", store));

                }
                else
                {
                    dbh.ExecuteNoneQuery("update [" + tableName + "] set "
                    + "[HeadersData]=@HeadersData,[BodyData]=@BodyData,[CreatedDate]=@CreatedDate,[ExpiresAbsolute]=@ExpiresAbsolute,[Seconds]=@Seconds,[date]=@date,[store]=@store "
                    + " where [KEY]=@KEY and [Type]=@Type"

                    , dbh.CreateParameter("@KEY", data.Key)
                    , dbh.CreateParameter("@Type", data.Type)

                    , dbh.CreateParameter("@HeadersData", data.HeadersData)
                    , dbh.CreateParameter("@BodyData", data.BodyData)

                    , dbh.CreateParameter("@CreatedDate", data.CreatedDate)
                    , dbh.CreateParameter("@ExpiresAbsolute", data.ExpiresAbsolute)
                    , dbh.CreateParameter("@Seconds", data.Seconds)
                    , dbh.CreateParameter("@date", DateTime.Now)
                    , dbh.CreateParameter("@store", store));
                }
            }
            catch (Exception ex)
            {

            }


            DoSave();
            DoClear();
        }


        void DoSave()
        {
            if (IsDoing())
            {
                return;
            }

            DateTime date = DateTime.Now;
            string cacheKey = "pagecache_store_datalist_prevdate";
            var obj = HttpRuntime.Cache.Get(cacheKey);
            if (obj != null)
            {
                date = (DateTime)obj;
            }
            else
            {
                HttpRuntime.Cache.Insert(cacheKey, DateTime.Now);
            }

            if ((DateTime.Now - date).TotalSeconds > this.setting.Config.ListStoreSeconds)
            {
                HttpRuntime.Cache[cacheKey] = DateTime.Now;

                if (IsDoing())
                {
                    return;
                }

                ThreadPool.QueueUserWorkItem(DoSaveAsync);
            }
        }


        const string doingCacheKey = "pagecache_store_datalist_doing";

        bool IsDoing()
        {
            bool doing = false;
            var obj = HttpRuntime.Cache.Get(doingCacheKey);
            if (obj != null)
            {
                doing = (bool)obj;
            }
            else
            {
                HttpRuntime.Cache.Insert(doingCacheKey, false);
            }

            return doing;
        }

        void DoSaveAsync(object o)
        {

            DoSaveing();
        }


        void DoSaveing()
        {
            if (IsDoing())
            {
                return;
            }

            HttpRuntime.Cache[doingCacheKey] = true;

            var date = DateTime.Now;

            string sql = "select top 100 [KEY],[Type],[CreatedDate],[ExpiresAbsolute],[Seconds],[HeadersData],[BodyData],[store] "
              + " from [" + tableName + "]   where [date]<@date";

        loop:

            bool have = false;

            HttpRuntime.Cache[doingCacheKey] = true;

            try
            {
                using (var reader = dbh.ExecuteReader(sql, dbh.CreateParameter("@date", date)))
                {
                    while (reader.Read())
                    {
                        have = true;

                        var data = new Store.StoreData();

                        data.Key = reader.GetString(0);
                        data.Type = reader.GetString(1);
                        data.CreatedDate = reader.GetDateTime(2);
                        data.ExpiresAbsolute = reader.GetDateTime(3);
                        data.Seconds = reader.GetInt32(4);
                        data.HeadersData = (byte[])reader.GetValue(5);
                        data.BodyData = (byte[])reader.GetValue(6);
                        string storeName = reader.GetString(7);
                        var store = setting.Stores.Get(storeName);

                        if (store != null)
                        {
                            store.Save(data);
                        }                    

                        Delete(data.Type, data.Key);
                    }
                    reader.Close();
                    reader.Dispose();
                }
            }
            catch (Exception ex)
            {
                
            }            


            if (have)
            {
                goto loop;
            }

            HttpRuntime.Cache[doingCacheKey] = false;
        }


        void DoClear()
        {
            if (IsClearing())
            {
                return;
            }

            DateTime date = DateTime.Now;
            string cacheKey = "pagecache_store_datalist_clear_prevdate";
            var obj = HttpRuntime.Cache.Get(cacheKey);
            if (obj != null)
            {
                date = (DateTime)obj;

            }
            else
            {
                HttpRuntime.Cache.Insert(cacheKey, DateTime.Now);
            }


            if ((DateTime.Now - date).TotalSeconds > this.setting.Config.ListStoreClearSeconds)
            {
                HttpRuntime.Cache[cacheKey] = DateTime.Now;

                if (IsClearing())
                {
                    return;
                }

                ThreadPool.QueueUserWorkItem(DoClearAsync);         
            }
        }

        const string clearingCacheKey = "pagecache_store_datalist_clearing";

        bool IsClearing()
        {
            bool doing = false;
            var obj = HttpRuntime.Cache.Get(clearingCacheKey);
            if (obj != null)
            {
                doing = (bool)obj;
            }
            else
            {
                HttpRuntime.Cache.Insert(clearingCacheKey, false);
            }

            return doing;
        }


        void DoClearAsync(object o)
        {
            DoClearing();
        }


        void DoClearing()
        {
            if (IsClearing())
            {
                return;
            }

            HttpRuntime.Cache[clearingCacheKey] = true;

            var date = DateTime.Now.AddSeconds(-this.setting.Config.ListStoreKeepSeconds);

            string sql = "select top 100 [KEY],[Type] from [" + tableName + "]   where [date]<@date";

        loop:

            bool have = false;

            HttpRuntime.Cache[clearingCacheKey] = true;

            try
            {
                using (var reader = dbh.ExecuteReader(sql, dbh.CreateParameter("@date", date)))
                {
                    while (reader.Read())
                    {
                        have = true;

                        string Key = reader.GetString(0);

                        string Type = reader.GetString(1);

                        Delete(Type, Key);
                    }
                    reader.Close();
                    reader.Dispose();
                }
            }
            catch (Exception ex)
            {

            }
   


            if (have)
            {
                goto loop;
            }

            HttpRuntime.Cache[clearingCacheKey] = false;
        }


        public void Delete(string type, string key)
        {
            try
            {
                dbh.ExecuteNoneQuery("delete from  [" + tableName + "] where  [key]=@key and [type]=@type"
                   , dbh.CreateParameter("@key", key)
                   , dbh.CreateParameter("@type", type));
            }
            catch (Exception ex)
            {

            }
           
        }
    }

}
